﻿/* 
 * PROJECT: NyMmd for C#
 * --------------------------------------------------------------------------------
 * The MMD for C# is C# version MMD Motion player class library.
 * NyMmd is modules which removed the ARToolKit origin codes from ARTK_MMD,
 * and was ported to C#. 
 *
 * This is based on the ARTK_MMD v0.1 by PY.
 * http://ppyy.if.land.to/artk_mmd.html
 * py1024<at>gmail.com
 * http://www.nicovideo.jp/watch/sm7398691
 *
 * 
 * The MIT License
 * Copyright (C)2008-2012 nyatla
 * nyatla39<at>gmail.com
 * http://nyatla.jp
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 * 
 */
using System;
using System.Collections.Generic;
using System.Collections;
using System.Text;
using System.IO;
using jp.nyatla.nymmd.cs.types;
using jp.nyatla.nymmd.cs.core;
using jp.nyatla.nymmd.cs.struct_type;
using jp.nyatla.nymmd.cs.struct_type.pmd;

namespace jp.nyatla.nymmd.cs
{



    class DataComparator : IComparer<PmdIK>
    {
        public int Compare(PmdIK o1, PmdIK o2)
        {
            return (int)(o1.getSortVal() - o2.getSortVal());
        }
    }

    public class MmdPmdModel
    {
        private String _name;	// モデル名
        private int _number_of_vertex;	// 頂点数

        private PmdFace[] m_pFaceArray; // 表情配列
        private PmdBone[] m_pBoneArray; // ボーン配列
        private PmdIK[] m_pIKArray;    // IK配列

        private MmdVector3[] _position_array;	// 座標配列	
        private MmdVector3[] _normal_array;		// 法線配列
        private MmdTexUV[] _texture_uv;		// テクスチャ座標配列
        private PmdSkinInfo[] _skin_info_array;
        private PmdMaterial[] _materials;		// マテリアル配列
        private short[] _indices_array;

        public MmdPmdModel(Stream i_stream)
        {
            initialize(new BinaryReader(i_stream));
            return;
        }
        public MmdPmdModel(BinaryReader i_reader)
        {
            initialize(i_reader);
            return;
        }
        public MmdPmdModel(StreamReader i_reader)
        {
            initialize(new BinaryReader(i_reader.BaseStream));
            return;
        }

        public int getNumberOfVertex()
        {
            return this._number_of_vertex;
        }
        public PmdMaterial[] getMaterials()
        {
            return this._materials;
        }
        public MmdTexUV[] getUvArray()
        {
            return this._texture_uv;
        }
        public MmdVector3[] getPositionArray()
        {
            return this._position_array;
        }
        public MmdVector3[] getNormatArray()
        {
            return this._normal_array;
        }
        public PmdSkinInfo[] getSkinInfoArray()
        {
            return this._skin_info_array;
        }
        public PmdFace[] getFaceArray()
        {
            return this.m_pFaceArray;
        }
        public PmdBone[] getBoneArray()
        {
            return this.m_pBoneArray;
        }
        public PmdIK[] getIKArray()
        {
            return this.m_pIKArray;
        }
        public short[] getIndicsArray()
        {
            return this._indices_array;
        }

        public PmdBone getBoneByName(String i_name)
        {
            PmdBone[] bone_array = this.m_pBoneArray;
            for (int i = 0; i < bone_array.Length; i++)
            {
                PmdBone bone = bone_array[i];
                if (bone.getName().Equals(i_name))
                    return bone;
            }
            return null;
        }
        public PmdFace getFaceByName(String i_name)
        {
            PmdFace[] face_array = this.m_pFaceArray;
            for (int i = 0; i < face_array.Length; i++)
            {
                PmdFace face = face_array[i];
                if (face.getName().Equals(i_name))
                    return face;
            }
            return null;
        }



        private void initialize(BinaryReader i_reader)
        {
            DataReader reader = new DataReader(i_reader);



            PMD_Header pPMDHeader = new PMD_Header();
            pPMDHeader.read(reader);
            if (!pPMDHeader.szMagic.Equals("PMD",StringComparison.CurrentCultureIgnoreCase))
            {
                throw new MmdException();
            }

            this._name = pPMDHeader.szName;

            // -----------------------------------------------------
            // 頂点数取得
            this._number_of_vertex = reader.readInt();//
            if (this._number_of_vertex < 0)
            {
                throw new MmdException();
            }

            // 頂点配列をコピー
            this._position_array = MmdVector3.createArray(this._number_of_vertex);
            this._normal_array = MmdVector3.createArray(this._number_of_vertex);
            this._texture_uv = MmdTexUV.createArray(this._number_of_vertex);
            this._skin_info_array = new PmdSkinInfo[this._number_of_vertex];

            PMD_Vertex tmp_pmd_vertex = new PMD_Vertex();
            for (int i = 0; i < _number_of_vertex; i++)
            {
                tmp_pmd_vertex.read(reader);
                
                _position_array[i].setValue(tmp_pmd_vertex.vec3Pos);
                _normal_array[i].setValue(tmp_pmd_vertex.vec3Normal);
                _texture_uv[i].setValue(tmp_pmd_vertex.uvTex);

                this._skin_info_array[i] = new PmdSkinInfo();
                this._skin_info_array[i].fWeight = tmp_pmd_vertex.cbWeight / 100.0f;
                this._skin_info_array[i].unBoneNo_0 = tmp_pmd_vertex.unBoneNo[0];
                this._skin_info_array[i].unBoneNo_1 = tmp_pmd_vertex.unBoneNo[1];
            }
            // -----------------------------------------------------
            // 頂点インデックス数取得
            this._indices_array = createIndicesArray(reader);


            // -----------------------------------------------------
            // マテリアル数取得
            int number_of_materials = reader.readInt();

            // マテリアル配列をコピー
            this._materials = new PmdMaterial[number_of_materials];

            PMD_Material tmp_pmd_material = new PMD_Material();

            int indices_ptr = 0;
            for (int i = 0; i < number_of_materials; i++)
            {
                tmp_pmd_material.read(reader);

                this._materials[i] = new PmdMaterial();
                this._materials[i].unknown = tmp_pmd_material.unknown;

                this._materials[i].start_of_indics =indices_ptr;
                this._materials[i].number_of_indics=tmp_pmd_material.ulNumIndices;
                indices_ptr += tmp_pmd_material.ulNumIndices;

                this._materials[i].col4Diffuse.setValue(tmp_pmd_material.col4Diffuse);

                this._materials[i].col4Specular.r = tmp_pmd_material.col3Specular.r;
                this._materials[i].col4Specular.g = tmp_pmd_material.col3Specular.g;
                this._materials[i].col4Specular.b = tmp_pmd_material.col3Specular.b;
                this._materials[i].col4Specular.a = 1.0f;

                this._materials[i].col4Ambient.r = tmp_pmd_material.col3Ambient.r;
                this._materials[i].col4Ambient.g = tmp_pmd_material.col3Ambient.g;
                this._materials[i].col4Ambient.b = tmp_pmd_material.col3Ambient.b;
                this._materials[i].col4Ambient.a = 1.0f;

                this._materials[i].fShininess = tmp_pmd_material.fShininess;

                this._materials[i].texture_name = tmp_pmd_material.szTextureFileName;
                if (this._materials[i].texture_name.Length < 1)
                {
                    this._materials[i].texture_name = null;
                }
            }

            //Boneの読み出し
            this.m_pBoneArray = createBoneArray(reader);
            //IK配列の読み出し
            this.m_pIKArray = createIKArray(reader, this.m_pBoneArray);
            //Face配列の読み出し
            this.m_pFaceArray = createFaceArray(reader);
            return;
        }
        private static short[] createIndicesArray(DataReader i_reader)
        {
            int num_of_indeces = i_reader.readInt();
            short[] result = new short[num_of_indeces];

            // 頂点インデックス配列をコピー
            for (int i = 0; i < num_of_indeces; i++)
            {
                result[i] = i_reader.readShort();
            }
            return result;
        }
        private static PmdBone[] createBoneArray(DataReader i_reader)
        {
            int number_of_bone = i_reader.readShort();
            PMD_Bone tmp_pmd_bone = new PMD_Bone();

            PmdBone[] result = new PmdBone[number_of_bone];
            for (int i = 0; i < number_of_bone; i++)
            {
                tmp_pmd_bone.read(i_reader);
                //ボーンの親子関係を一緒に読みだすので。
                result[i] = new PmdBone(tmp_pmd_bone, result);
            }
            for (int i = 0; i < number_of_bone; i++)
            {
                result[i].recalcOffset();
            }
            return result;
        }

        private static PmdIK[] createIKArray(DataReader i_reader, PmdBone[] i_ref_bone_array)
        {
            int number_of_ik = i_reader.readShort();
            PMD_IK tmp_pmd_ik = new PMD_IK();
            PmdIK[] result = new PmdIK[number_of_ik];
            // IK配列を作成
            if (number_of_ik > 0)
            {

                for (int i = 0; i < number_of_ik; i++)
                {
                    tmp_pmd_ik.read(i_reader);
                    result[i] = new PmdIK(tmp_pmd_ik, i_ref_bone_array);
                }
                System.Array.Sort<PmdIK>(result, new DataComparator());
            }
            return result;
        }

        private static PmdFace[] createFaceArray(DataReader i_reader)
        {
            int number_of_face = i_reader.readShort();
            PMD_FACE tmp_pmd_face = new PMD_FACE();
            PmdFace[] result = new PmdFace[number_of_face];

            // 表情配列を作成
            if (number_of_face > 0)
            {

                for (int i = 0; i < number_of_face; i++)
                {
                    tmp_pmd_face.read(i_reader);
                    result[i] = new PmdFace(tmp_pmd_face, result[0]);
                }
            }
            return result;
        }




        public String getModelName()
        {
            return this._name;
        }

    }

}
